/*******************************************************************************
 * Copyright (c) 2008 Scott Stanchfield.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Based on the ANTLR parser generator by Terence Parr, http://antlr.org
 *   Ric Klaren <klaren@cs.utwente.nl>
 *   Scott Stanchfield - Modifications for XML Parsing
 *******************************************************************************/
package com.javadude.antxr.preprocessor;

import java.io.IOException;

import com.javadude.antxr.CodeGenerator;
import com.javadude.antxr.Tool;
import com.javadude.antxr.collections.impl.IndexedVector;

class Grammar {
    protected String name;
    protected String fileName;		// where does it come from?
    protected String superGrammar;	// null if no super class
    protected String type;				// lexer? parser? tree parser?
    protected IndexedVector<Rule> rules;	// text of rules as they were read in
    protected IndexedVector<Option> options;// rule options
    protected String tokenSection;	// the tokens{} stuff
    protected String preambleAction;// action right before grammar
    protected String memberAction;	// action inside grammar
    protected Hierarchy hier;			// hierarchy of grammars
    protected boolean predefined = false;	// one of the predefined grammars?
    protected boolean alreadyExpanded = false;
    protected boolean specifiedVocabulary = false;	// found importVocab option?

    /** if not derived from another grammar, might still specify a non-ANTXR
     *  class to derive from like this "class T extends Parser(MyParserClass);"
     */
    protected String superClass = null;

    protected String importVocab = null;
    protected String exportVocab = null;
    protected Tool antxrTool;

    public Grammar(Tool tool, String name, String superGrammar, IndexedVector<Rule> rules) {
        this.name = name;
        this.superGrammar = superGrammar;
        this.rules = rules;
        this.antxrTool = tool;
    }

    public void addOption(Option o) {
        if (options == null) {	// if not already there, create it
            options = new IndexedVector<Option>();
        }
        options.appendElement(o.getName(), o);
    }

    public void addRule(Rule r) {
        rules.appendElement(r.getName(), r);
    }

    /** Copy all nonoverridden rules, vocabulary, and options into this grammar from
     *  supergrammar chain.  The change is made in place; e.g., this grammar's vector
     *  of rules gets bigger.  This has side-effects: all grammars on path to
     *  root of hierarchy are expanded also.
     */
    public void expandInPlace() {
        // if this grammar already expanded, just return
        if (alreadyExpanded) {
            return;
        }

        // Expand super grammar first (unless it's a predefined or subgrammar of predefined)
        Grammar superG = getSuperGrammar();
        if (superG == null) {
            return; // error (didn't provide superclass)
        }
        if (exportVocab == null) {
            // if no exportVocab for this grammar, make it same as grammar name
            exportVocab = getName();
        }
        if (superG.isPredefined()) {
            return; // can't expand Lexer, Parser, ...
        }
        superG.expandInPlace();

        // expand current grammar now.
        alreadyExpanded = true;
        // track whether a grammar file needed to have a grammar expanded
        GrammarFile gf = hier.getFile(getFileName());
        gf.setExpanded(true);

        // Copy rules from supergrammar into this grammar
        IndexedVector<Rule> inhRules = superG.getRules();
        for (Rule r : inhRules) {
            inherit(r, superG);
        }

        // Copy options from supergrammar into this grammar
        // Modify tokdef options so that they point to dir of enclosing grammar
        IndexedVector<Option> inhOptions = superG.getOptions();
        if (inhOptions != null) {
            for (Option o : inhOptions) {
                inherit(o, superG);
            }
        }

        // add an option to load the superGrammar's output vocab
        if ((options != null && options.getElement("importVocab") == null) || options == null) {
            // no importVocab found, add one that grabs superG's output vocab
            Option inputV = new Option("importVocab", superG.exportVocab + ";", this);
            addOption(inputV);
            // copy output vocab file to current dir
            String originatingGrFileName = superG.getFileName();
            String path = antxrTool.pathToFile(originatingGrFileName);
            String superExportVocabFileName = path + superG.exportVocab +
                CodeGenerator.TokenTypesFileSuffix +
                CodeGenerator.TokenTypesFileExt;
            String newImportVocabFileName = antxrTool.fileMinusPath(superExportVocabFileName);
            if (path.equals("." + System.getProperty("file.separator"))) {
                // don't copy tokdef file onto itself (must be current directory)
                // System.out.println("importVocab file same dir; leaving as " + superExportVocabFileName);
            }
            else {
                try {
                    antxrTool.copyFile(superExportVocabFileName, newImportVocabFileName);
                }
                catch (IOException io) {
                    antxrTool.toolError("cannot find/copy importVocab file " + superExportVocabFileName);
                    return;
                }
            }
        }

        // copy member action from supergrammar into this grammar
        inherit(superG.memberAction, superG);
    }

    public String getFileName() {
        return fileName;
    }

    public String getName() {
        return name;
    }

    public IndexedVector<Option> getOptions() {
        return options;
    }

    public IndexedVector<Rule> getRules() {
        return rules;
    }

    public Grammar getSuperGrammar() {
        if (superGrammar == null) {
            return null;
        }
        Grammar g = hier.getGrammar(superGrammar);
        return g;
    }

    public String getSuperGrammarName() {
        return superGrammar;
    }

    public String getType() {
        return type;
    }

    public void inherit(Option o, Grammar superG) {
        // do NOT inherit importVocab/exportVocab options under any circumstances
        if (o.getName().equals("importVocab") ||
            o.getName().equals("exportVocab")) {
            return;
        }

        Option overriddenOption = null;
        if (options != null) {	// do we even have options?
            overriddenOption = (Option)options.getElement(o.getName());
        }
        // if overridden, do not add to this grammar
        if (overriddenOption == null) { // not overridden
            addOption(o);	// copy option into this grammar--not overridden
        }
    }

    public void inherit(Rule r, Grammar superG) {
        // if overridden, do not add to this grammar
        Rule overriddenRule = (Rule)rules.getElement(r.getName());
        if (overriddenRule != null) {
            // rule is overridden in this grammar.
            if (!overriddenRule.sameSignature(r)) {
                // warn if different sig
                antxrTool.warning("rule " + getName() + "." + overriddenRule.getName() +
                                   " has different signature than " +
                                   superG.getName() + "." + overriddenRule.getName());
            }
        }
        else {  // not overridden, copy rule into this
            addRule(r);
        }
    }

    public void inherit(String memberActionToInherit, Grammar superG) {
        if (this.memberAction != null) {
            return;	// do nothing if already have member action
        }
        if (memberActionToInherit != null) { // don't have one here, use supergrammar's action
            this.memberAction = memberActionToInherit;
        }
    }

    public boolean isPredefined() {
        return predefined;
    }

    public void setFileName(String f) {
        fileName = f;
    }

    public void setHierarchy(Hierarchy hier) {
        this.hier = hier;
    }

    public void setMemberAction(String a) {
        memberAction = a;
    }

    public void setOptions(IndexedVector<Option> options) {
        this.options = options;
    }

    public void setPreambleAction(String a) {
        preambleAction = a;
    }

    public void setPredefined(boolean b) {
        predefined = b;
    }

    public void setTokenSection(String tk) {
        tokenSection = tk;
    }

    public void setType(String t) {
        type = t;
    }

    @Override
    public String toString() {
        StringBuffer s = new StringBuffer(10000);
        if (preambleAction != null) {
            s.append(preambleAction);
        }
        if (superGrammar == null) {
            return "class " + name + ";";
        }
        if ( superClass!=null ) {
            // replace with specified superclass not actual grammar
            // user must make sure that the superclass derives from super grammar class
            s.append("class " + name + " extends " + superClass + ";");
        }
        else {
            s.append("class " + name + " extends " + type + ";");
        }
        s.append(
            System.getProperty("line.separator") +
            System.getProperty("line.separator"));
        if (options != null) {
            s.append(Hierarchy.optionsToString(options));
        }
        if (tokenSection != null) {
            s.append(tokenSection + "\n");
        }
        if (memberAction != null) {
            s.append(memberAction + System.getProperty("line.separator"));
        }
        for (int i = 0; i < rules.size(); i++) {
            Rule r = (Rule)rules.elementAt(i);
            if (!getName().equals(r.enclosingGrammar.getName())) {
                s.append("// inherited from grammar " + r.enclosingGrammar.getName() + System.getProperty("line.separator"));
            }
            s.append(r +
                System.getProperty("line.separator") +
                System.getProperty("line.separator"));
        }
        return s.toString();
    }
}
